#include<linux/module.h>
#include<linux/printk.h>
#include<linux/kobject.h>
#include<linux/kernel.h>
#include<linux/string.h>
#include<linux/kallsyms.h>
#include<linux/sched.h>
#include<linux/cpu.h>
#include<linux/pid_namespace.h>
#include<linux/mmzone.h>
#include<linux/nodemask.h>
#include<linux/mm.h>
#include<linux/page-flags.h>
#include<net/net_namespace.h>
#include<net/rtnetlink.h>
#include<linux/netdevice.h>
#include<linux/inetdevice.h>
#include<linux/mutex.h>
#include<linux/slab.h>
#include<linux/slub_def.h>
#include<linux/types.h>
#include <linux/proc_fs.h>
#include <linux/memcontrol.h>
#include <linux/namei.h>
#include <linux/version.h>
#include "common.h"

static unsigned long totalCache = 0;
static char * fileName = NULL;
struct filecache_result_list result_list;

static int dump_filecache_result(struct filecache_result __user *result)
{
    struct file_item *tmp, *fnext;
    struct filecache_result res;
    int count = 0;
    int i = 0;
    struct file_item_list *items, *tmp2;
    int ret = 0;

    if(copy_from_user(&res, result, sizeof(res)))
    {
        pr_err("can not copy from user %d:%d\n",count,__LINE__);
        ret = copy_to_user(result, &count, sizeof(count));
        return 0;
    }
    if (!res.num || !res.filecache_items)
    {
        pr_err("num %d ,items %p \n", res.num, res.filecache_items);
        ret = copy_to_user(result, &count, sizeof(count));
        return 0;
    }

    i = res.num > result_list.num ? result_list.num : res.num;
    if(i <= 0)
    {
    	i = copy_to_user(result, &count, sizeof(count));
    	return i;
    }
    if((tmp = vmalloc(sizeof(struct file_item) * i)) == NULL)
    {
        pr_err("vmalloc error %d:%d\n",count,__LINE__);
        ret = copy_to_user(result, &count, sizeof(count));
        return 0;
    }

    fnext = tmp;

    list_for_each_entry_safe(items, tmp2, &(result_list.file_items_list), items_list)
    {
        pr_err("filename:%s size:%lu cached:%lu deleted:%d\n", items->filename, items->size, items->cached, items->deleted);
        strcpy(fnext->filename, items->filename);
        fnext->size = items->size;
        fnext->cached = items->cached;
        fnext->deleted = items->deleted;
        count += 1;
        fnext ++;
        if(count >= i)
            break;
    }
    res.num = count;

    i = copy_to_user(result, &count, sizeof(count));
    i = copy_to_user(res.filecache_items, tmp, sizeof(struct file_item) * count);
    vfree(tmp);
    return i;
}

static void scan_super_block(struct super_block *sb, void * args)
{
    struct inode *inode, *next;
    unsigned long cached;
    int deleted;
    struct file_item_list *tmp, *tmp2 = NULL;

#ifdef LINUX_310
    spinlock_t *sb_inode_lock = kallsyms_lookup_name("inode_sb_list_lock");
    spin_lock(sb_inode_lock);
#else
    spin_lock(&sb->s_inode_list_lock);
#endif
    list_for_each_entry_safe(inode, next, &sb->s_inodes, i_sb_list) {
        if (S_ISREG(inode->i_mode)) {
            scan_inode_name(inode, fileName, PATH_MAX, &cached, &deleted);
            if (cached*4 > 1024*50)
            {
                pr_err("filename:%s size:%lu cached:%lu deleted:%d\n", fileName, (unsigned long)inode->i_size, cached, deleted);
                tmp2 = vzalloc(sizeof(struct file_item_list));
                if(!tmp2)
                {
                    pr_err("vzalloc error: %d",__LINE__);
                    break;
                }
                strncpy(tmp2->filename, fileName,strlen(fileName));
                tmp2->size = (unsigned long)inode->i_size;
                tmp2->cached = cached;
                tmp2->deleted = deleted;
                result_list.num += 1;
                list_add_tail(&tmp2->items_list, &(result_list.file_items_list));
            }
        }
    }
#ifdef LINUX_310
    spin_unlock(sb_inode_lock);
#else
    spin_unlock(&sb->s_inode_list_lock);
#endif
}

static int scan_filesystem_type(char * fs)
{
    struct file_system_type * file_system;

    file_system = get_fs_type(fs);
    if (!file_system)
        return 0;

    iterate_supers_type(file_system, scan_super_block, NULL);
    module_put(file_system->owner);

    return 0;
}

int  filecache_init(void)
{
    fileName = __getname();
    if(!fileName)
        return 0;
    result_list.num = 0;
    INIT_LIST_HEAD(&(result_list.file_items_list));
    return 0;
}

void filecache_exit(void)
{
    struct file_item_list *files_all, *tmp;
    if (fileName)
        __putname(fileName);
    if(result_list.num)
    {
        list_for_each_entry_safe(files_all, tmp, &(result_list.file_items_list), items_list)
            vfree(files_all);
    }
}

int filecache_scan(void)
{
    filecache_init();
    scan_filesystem_type("ext4");
    pr_err("total file cached %lu\n",totalCache);
    totalCache = 0;
    scan_filesystem_type("tmpfs");
    pr_err("total tmpfs  %lu\n",totalCache);
    filecache_exit();
    return 0;
}
int filecache_main(unsigned long arg)
{
    struct filecache_result *res = (struct filecache_result*)arg;
    int ret = 0;
    char fsname[NAME_LEN];
    memset(fsname, 0, sizeof(fsname));
    ret = copy_from_user(fsname, res->fsname, NAME_LEN);
    pr_err("fsname:%s\n",fsname);
    filecache_init();
    scan_filesystem_type(fsname);
    pr_err("total file cached %lu\n",totalCache);
    dump_filecache_result(res);
    return 0;
}
